package com.syl.starter.itools.framework.utils;

import com.syl.starter.itools.base.beans.DoubleResult;
import com.syl.starter.itools.base.constants.BaseConstant;
import com.syl.starter.itools.base.constants.TipsMessageConstant;
import com.syl.starter.itools.common.annotations.Verify;
import com.syl.starter.itools.common.utils.ReflectUtils;
import com.syl.starter.itools.common.utils.RegexUtils;
import com.syl.starter.itools.common.utils.StringUtils;
import com.syl.starter.itools.framework.beans.VerifyFailResult;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;


/**
 * 验证工具类
 * @author syl
 * @create 2018-09-14 11:06
 **/
public class VerifyUtils {
    private static Logger LOG = LoggerFactory.getLogger(VerifyUtils.class);

    /**
     * 获取校验器
     * @param fast 是否快速失败
     * @return
     */
    public static Validator getValidator(boolean fast){
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(fast)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

    /**
     * 验证bean 对象 直至一个验证失败
     * @param cz 待验证bean对象
     * @return 验证成功true, VerifyFailResult 为空 否则为false, 失败信息
     */
    public static DoubleResult<Boolean, VerifyFailResult> validateOne(Object cz){
        return validateOne(cz, new Class[]{});
    }

    /**
     * 验证bean 对象 直至一个验证失败
     * @param cz 待验证bean对象
     * @param groups 分组类型
     * @return 验证成功true, VerifyFailResult 为空 否则为false, 失败信息
     */
    public static DoubleResult<Boolean, VerifyFailResult> validateOne(Object cz, Class<?>... groups){
        if(cz == null)
            return new DoubleResult(false,new VerifyFailResult().setMessage(TipsMessageConstant.BASE_EMPTY_PARAM));
        Validator validator = getValidator(true);
        Set<ConstraintViolation<Object>> validate =
                groups == null
                ? validator.validate(cz)
                : validator.validate(cz, groups);
        return packageOne(validate);
    }

    /**
     * 验证bean 对象 验证所有字段
     * @param cz 待验证bean对象
     * @return
     */
    public static DoubleResult<Boolean,List<VerifyFailResult>> validateAll(Object cz){
        return validateAll(cz, new Class[]{});
    }

    /**
     * 验证bean 对象 验证所有字段
     * @param cz 待验证bean对象
     * @param groups 分组类型
     * @return
     */
    public static DoubleResult<Boolean,List<VerifyFailResult>> validateAll(Object cz, Class<?>... groups) {
        Validator validator = getValidator(false);
        Set<ConstraintViolation<Object>> validate =
                groups == null
                ? validator.validate(cz)
                : validator.validate(cz, groups);
        return packageList(validate);
    }

    /**
     * 验证方法参数 直至一个验证失败 (无重载方法时可用)
     * @param instance 对象实例
     * @param methodName 方法名
     * @param paramValues 传入方法参数
     * @return
     */
    public static DoubleResult<Boolean, VerifyFailResult> validateParamOne(Object instance, String methodName, Object... paramValues){
        return validateParamOne(instance,methodName,paramValues,null);
    }

    /**
     * 验证方法参数 直至一个验证失败
     * @param instance 对象实例
     * @param methodName 方法名
     * @param paramValues 传入方法参数
     * @param alias 方法别名 (如果含有同名重载方法必须使用@MethodAlias 来对方法重命名)
     * @return
     */
    public static DoubleResult<Boolean, VerifyFailResult> validateParamOne(Object instance, String methodName, Object[] paramValues, String alias){
        Class<?>[] paramType = ReflectUtils.getMethodParamType(instance.getClass(), methodName, alias);
        Object[] objects = paramValuesPolishing(paramType.length, methodName, paramValues);
        Method method = ReflectUtils.getMethod(instance.getClass(), methodName, alias);
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Verify annotation = parameter.getAnnotation(Verify.class);
            if(annotation != null && annotation.value()){
                DoubleResult<Boolean, VerifyFailResult> validateObject = validateOne(objects[i], annotation.groups());
                if(!validateObject.getState()){
                    String field = validateObject.getData().getField();
                    validateObject.getData().setField(parameter.getName() + (StringUtils.isEmpty(field) ? BaseConstant.EMPTY_STRING : BaseConstant.DOT + field));
                    return validateObject;
                }
            }
        }
        Validator validator = getValidator(true);
        //2.获取校验方法参数的校验器
        ExecutableValidator validatorParam = validator.forExecutables();
        Set<ConstraintViolation<Object>> violations = validatorParam.validateParameters(instance, method, objects);
        return packageOne(violations, methodName + BaseConstant.DOT);
    }

    /**
     * 参数值个数补齐
     * @param paramTypeSize
     * @param paramValues
     * @return
     */
    private static Object[] paramValuesPolishing(int paramTypeSize, String methodName, Object... paramValues){
        int length = paramValues.length;
        Object[] objects = new Object[length];
        if(paramTypeSize < length) {
            LOG.info("可能存在同名方法 会导致报错 疑似方法名为 {}", methodName);
        }
        LOG.debug("方法真实参数个数 {} 传入个数 {}", paramTypeSize, length);
        for (int i = 0; i < length; i++) {
            objects[i] = paramValues[i];
        }
        return objects;
    }

    private static DoubleResult<Boolean, VerifyFailResult> packageOne(Set<ConstraintViolation<Object>> validate){
        return packageOne(validate,null);
    }

    /**
     * 重新包装验证结果 单个
     * @param validate
     * @return
     */
    private static DoubleResult<Boolean, VerifyFailResult> packageOne(Set<ConstraintViolation<Object>> validate, String fieldReplace){
        VerifyFailResult result = null;
        DoubleResult<Boolean, VerifyFailResult> doubleResult = new DoubleResult<>();
        for (ConstraintViolation<Object> violation : validate) {
            result = new VerifyFailResult();
            String propPath = violation.getPropertyPath().toString();
            result.setField(fieldReplace == null ? propPath : propPath.replace(fieldReplace,BaseConstant.EMPTY_STRING));
            result.setMessage(violation.getMessage());
            break;
        }
        doubleResult.setState(validate.isEmpty()).setData(result);
        return doubleResult;
    }

    /**
     * 重新包装验证结果 所有
     * @param validate
     * @return
     */
    private static DoubleResult<Boolean,List<VerifyFailResult>> packageList(Set<ConstraintViolation<Object>> validate){
        List<VerifyFailResult> result = validate.size() < 1 ? null : new ArrayList<>();
        DoubleResult<Boolean,List<VerifyFailResult>> doubleResult = new DoubleResult<>();
        for (ConstraintViolation<Object> violation : validate) {
            String field = violation.getPropertyPath().toString();
            result.add(new VerifyFailResult(field, violation.getMessage()));
        }
        doubleResult.setState(validate.isEmpty()).setData(result);
        return doubleResult;
    }

    /**
     * 验证帮助方法
     * @param regex 正则表达式
     * @param fieldName 字段名称
     * @param string 待验证字符串
     * @param message 验证失败提示信息
     * @param required 必填
     * @return  验证成功true, VerifyFailResult 为空 否则为false, 失败信息
     */
    private static DoubleResult<Boolean, VerifyFailResult> verifyHelp(String regex, String fieldName, String string, String message, boolean required){
        boolean b = (required ? !StringUtils.trimToEmpty(string).isEmpty() : true) &&
                RegexUtils.test(regex,string);
        DoubleResult<Boolean, VerifyFailResult> result = new DoubleResult<>(b);
        if(!b)
            result.setData(new VerifyFailResult(fieldName, message));
        return result;
    }

}
